//******************************************************************
//******************************************************************
//**********          ANts Peer To Peer Sources        *************
//
// ANts P2P realizes a third generation P2P net. It protects your
// privacy while you are connected and makes you not trackable, hiding
// your identity (ip) and crypting everything you are sending/receiving
// from others.

// Copyright (C) 2004  Roberto Rossi

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

package ants.p2p.security.sockets;

import java.net.*;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.security.interfaces.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
import com.sun.crypto.provider.SunJCE;
import java.util.*;

import ants.p2p.*;

import org.apache.log4j.*;

public class SecureClientSocketThread {
  public static final String cipher = "AES";
  public static final int cipherKeySize = 16;
  public static final int DHKeyExchangeBits = 512;
  public static final int MinDHKeyExchangeBits = 512;

  static Logger _logger = Logger.getLogger(SecureClientSocketThread.class.getName());

  DHParameterSpec dhParamSpec;
  KeyPair clientKpair;
  KeyAgreement clientKeyAgree;
  PublicKey serverPubKey;
  PublicKey clientPubKey;

  byte[] sharedSecret;
  byte[] aesKey;
  Cipher clientCipherEnc;
  Cipher clientCipherDec;

  long timeElapsed;
  int pingSize = 32;

  boolean newVersionDetected = false;
  String newerVersion = Ant.getVersion();

  SecureClientSocket localSocket;
  InputStream socketInputStream;
  OutputStream socketOutputStream;

  public SecureClientSocketThread(SecureClientSocket localSocket) throws IOException,
      NoSuchAlgorithmException,
      InvalidParameterSpecException,
      InvalidAlgorithmParameterException,
      InvalidKeyException {
    this.localSocket = localSocket;
  }

  private void generateDHParameters()throws IOException,
      NoSuchAlgorithmException,
      InvalidParameterSpecException,
      InvalidAlgorithmParameterException,
      InvalidKeyException {
    /*AlgorithmParameterGenerator paramGen = AlgorithmParameterGenerator.
        getInstance("DH");
    paramGen.init(512);
    AlgorithmParameters params = paramGen.generateParameters();
    dhParamSpec = (DHParameterSpec) params.getParameterSpec(DHParameterSpec.class);
    */
    if(skip1024Modulus.bitLength() < SecureClientSocketThread.MinDHKeyExchangeBits){
      throw new InvalidAlgorithmParameterException(
          "Security Error: key size < "+SecureClientSocketThread.MinDHKeyExchangeBits);
    }
    dhParamSpec = new DHParameterSpec(skip1024Modulus, skip1024Base);

    KeyPairGenerator clientKpairGen = KeyPairGenerator.getInstance("DH");
    clientKpairGen.initialize(dhParamSpec);
    clientKpair = clientKpairGen.generateKeyPair();

    clientKeyAgree = KeyAgreement.getInstance("DH");
    clientKeyAgree.init(clientKpair.getPrivate());

    clientPubKey = clientKpair.getPublic();
  }

  public void run(){
    try{
      _logger.info("Client is Accepting...");
      this.clientCipherEnc = Cipher.getInstance(SecureClientSocketThread.cipher);
      this.clientCipherDec = Cipher.getInstance(SecureClientSocketThread.cipher);

      this.socketInputStream = localSocket.getInputStream();
      this.socketOutputStream = localSocket.getOutputStream();

      ObjectOutputStream keyAgreementOutStream = new ObjectOutputStream(this.socketOutputStream);
      ObjectInputStream keyAgreementInStream = new ObjectInputStream(this.socketInputStream);

      keyAgreementOutStream.writeObject(Ant.getProtocolVersion());
      String protocolVersion = (String)keyAgreementInStream.readObject();
      if(!protocolVersion.equals(Ant.getProtocolVersion())){
         if (!protocolVersion.startsWith("beta") &&
                 protocolVersion.compareTo(Ant.getProtocolVersion()) > 0) {
           this.newVersionDetected = true;
           this.newerVersion = "Protocol update: " + protocolVersion;
         }
         this.localSocket.close();
         return;
      }
      keyAgreementOutStream.writeObject(Ant.getApplicationName());
      keyAgreementOutStream.writeObject(Ant.getVersion());
      String applicationName = (String)keyAgreementInStream.readObject();
      if(applicationName.equals(Ant.getApplicationName())){
        String version = (String) keyAgreementInStream.readObject();
        if (!version.equals(Ant.getVersion())) {
          if (version.compareTo(Ant.getVersion()) > 0) {
            this.newVersionDetected = true;
            this.newerVersion = version;
          }
        }
      }else{
        keyAgreementInStream.readObject();
      }
      keyAgreementOutStream.writeInt(this.localSocket.getLocalServerPort());

      keyAgreementOutStream.writeObject(new byte[pingSize]);
      keyAgreementOutStream.flush();

      long startProbe = System.currentTimeMillis();
      keyAgreementInStream.readObject();
      timeElapsed = System.currentTimeMillis() - startProbe;

      /*this.skip1024Base = new BigInteger((String)keyAgreementInStream.readObject());
      this.skip1024Modulus = new BigInteger((String)keyAgreementInStream.readObject());
      this.generateDHParameters();
      BigInteger y = new BigInteger((String)keyAgreementInStream.readObject());
      DHPublicKeySpec serverPubKeySpec = new DHPublicKeySpec(y, this.dhParamSpec.getP(),this.dhParamSpec.getG());
      this.serverPubKey = KeyFactory.getInstance("DH").generatePublic(serverPubKeySpec);
      keyAgreementOutStream.writeObject(((DHPublicKey)clientPubKey).getY().toString());
      keyAgreementOutStream.flush();

      clientKeyAgree.doPhase(serverPubKey, true);

      this.sharedSecret = clientKeyAgree.generateSecret();

      this.aesKey = new byte[SecureClientSocketThread.cipherKeySize];//AES 16
      for (int x = 0; x < this.aesKey.length; x++) {
        this.aesKey[x] = this.sharedSecret[x];
      }
      SecretKeySpec clientKeySpec = new SecretKeySpec(aesKey, SecureClientSocketThread.cipher);
      this.clientCipherEnc.init(Cipher.ENCRYPT_MODE, clientKeySpec);
      this.clientCipherDec.init(Cipher.DECRYPT_MODE, clientKeySpec);*/

      _logger.info("Neighbourg accepted.");
    }
    catch (Exception e) {
      try{
        this.localSocket.close();
      }catch(Exception ex){}
      _logger.info("Failed secure connection with " +
                   localSocket.getInetAddress() + ":" + localSocket.getPort() +
                   " :" + e.getMessage(),e);
      _logger.debug("Failed secure connection ",e);
    }
  }

  public boolean isNewVersionDetected(){
    return this.newVersionDetected;
  }

  public String getNewerVersion(){
    return this.newerVersion;
  }

  public long getTimeElapsed(){
    return this.timeElapsed;
  }

  public Cipher getCipherEnc() {
    return this.clientCipherEnc;
  }

  public Cipher getCipherDec() {
    return this.clientCipherDec;
  }

  public SecureClientSocket getSocket(){
    return this.localSocket;
  }


  // The 1024 bit Diffie-Hellman modulus values used by SKIP
  private static final byte skip1024ModulusBytes[] = {
      (byte) 0xF4, (byte) 0x88, (byte) 0xFD, (byte) 0x58,
      (byte) 0x4E, (byte) 0x49, (byte) 0xDB, (byte) 0xCD,
      (byte) 0x20, (byte) 0xB4, (byte) 0x9D, (byte) 0xE4,
      (byte) 0x91, (byte) 0x07, (byte) 0x36, (byte) 0x6B,
      (byte) 0x33, (byte) 0x6C, (byte) 0x38, (byte) 0x0D,
      (byte) 0x45, (byte) 0x1D, (byte) 0x0F, (byte) 0x7C,
      (byte) 0x88, (byte) 0xB3, (byte) 0x1C, (byte) 0x7C,
      (byte) 0x5B, (byte) 0x2D, (byte) 0x8E, (byte) 0xF6,
      (byte) 0xF3, (byte) 0xC9, (byte) 0x23, (byte) 0xC0,
      (byte) 0x43, (byte) 0xF0, (byte) 0xA5, (byte) 0x5B,
      (byte) 0x18, (byte) 0x8D, (byte) 0x8E, (byte) 0xBB,
      (byte) 0x55, (byte) 0x8C, (byte) 0xB8, (byte) 0x5D,
      (byte) 0x38, (byte) 0xD3, (byte) 0x34, (byte) 0xFD,
      (byte) 0x7C, (byte) 0x17, (byte) 0x57, (byte) 0x43,
      (byte) 0xA3, (byte) 0x1D, (byte) 0x18, (byte) 0x6C,
      (byte) 0xDE, (byte) 0x33, (byte) 0x21, (byte) 0x2C,
      (byte) 0xB5, (byte) 0x2A, (byte) 0xFF, (byte) 0x3C,
      (byte) 0xE1, (byte) 0xB1, (byte) 0x29, (byte) 0x40,
      (byte) 0x18, (byte) 0x11, (byte) 0x8D, (byte) 0x7C,
      (byte) 0x84, (byte) 0xA7, (byte) 0x0A, (byte) 0x72,
      (byte) 0xD6, (byte) 0x86, (byte) 0xC4, (byte) 0x03,
      (byte) 0x19, (byte) 0xC8, (byte) 0x07, (byte) 0x29,
      (byte) 0x7A, (byte) 0xCA, (byte) 0x95, (byte) 0x0C,
      (byte) 0xD9, (byte) 0x96, (byte) 0x9F, (byte) 0xAB,
      (byte) 0xD0, (byte) 0x0A, (byte) 0x50, (byte) 0x9B,
      (byte) 0x02, (byte) 0x46, (byte) 0xD3, (byte) 0x08,
      (byte) 0x3D, (byte) 0x66, (byte) 0xA4, (byte) 0x5D,
      (byte) 0x41, (byte) 0x9F, (byte) 0x9C, (byte) 0x7C,
      (byte) 0xBD, (byte) 0x89, (byte) 0x4B, (byte) 0x22,
      (byte) 0x19, (byte) 0x26, (byte) 0xBA, (byte) 0xAB,
      (byte) 0xA2, (byte) 0x5E, (byte) 0xC3, (byte) 0x55,
      (byte) 0xE9, (byte) 0x2F, (byte) 0x78, (byte) 0xC7
  };
/*
  private static byte[] iv = new byte[]{
                (byte)0x8E, 0x12, 0x39, (byte)0x9C,
                0x07, 0x72, 0x6F, 0x5A
            };
*/
// The SKIP 1024 bit modulus
  private BigInteger skip1024Modulus
      = new BigInteger(1, skip1024ModulusBytes);

// The base used with the SKIP 1024 bit modulus
  private BigInteger skip1024Base = BigInteger.valueOf(2);
}

